/*
 * Copyright (C) 2017 Samuel Wall
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package uk.co.samuelwall.materialtaptargetprompt.extras;

import ohos.agp.animation.Animator;
import ohos.agp.components.Component;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.StateElement;
import ohos.agp.render.BlendMode;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.Point;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;
import ohos.media.image.PixelMap;
import uk.co.samuelwall.materialtaptargetprompt.Drawable;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import uk.co.samuelwall.materialtaptargetprompt.ResourceFinder;
import uk.co.samuelwall.materialtaptargetprompt.extras.backgrounds.CirclePromptBackground;
import uk.co.samuelwall.materialtaptargetprompt.extras.focals.CirclePromptFocal;
import uk.co.samuelwall.materialtaptargetprompt.utils.Constants;
import uk.co.samuelwall.materialtaptargetprompt.utils.DensityUtils;

import org.jetbrains.annotations.Nullable;

import java.io.IOException;

/**
 * Contains all the settings for creating a prompt.
 *
 * @param <T> The subclass that extends this.
 * @since 2021-05-28
 */
public class PromptOptions<T extends PromptOptions> {
    /**
     * The {@link ResourceFinder} used to find views and resources.
     */
    private ResourceFinder mResourceFinder;

    /**
     * Has the target been set successfully.
     */
    private boolean isTargetSet;

    /**
     * The view to place the prompt around.
     */
    @Nullable
    private Component mTargetView;

    /**
     * The left and top positioning for the focal centre point.
     */
    @Nullable
    private Point mTargetPosition;

    /**
     * The primary text to display.
     */
    @Nullable
    private CharSequence mPrimaryText;

    /**
     * The secondary text to display.
     */
    @Nullable
    private CharSequence mSecondaryText;

    /**
     * The colour for the primary text.
     */
    private int mPrimaryTextColour = Color.WHITE.getValue();

    /**
     * The colour for the secondary text.
     */
    private int mSecondaryTextColour = Color.argb(
            Constants.NUM_179, Constants.NUM_255, Constants.NUM_255, Constants.NUM_255);

    /**
     * The colour for the prompt background.
     */
    private int mBackgroundColour = Color.argb(
            Constants.NUM_244, Constants.NUM_63, Constants.NUM_81, Constants.NUM_181);

    /**
     * The colour for the prompt focal.
     */
    private int mFocalColour = Color.WHITE.getValue();

    /**
     * The circle focal implementation radius.
     */
    private float mFocalRadius;

    /**
     * The primary text font size.
     */
    private float mPrimaryTextSize;

    /**
     * The secondary text font size.
     */
    private float mSecondaryTextSize;

    /**
     * The maximum width to allow the text to be.
     */
    private float mMaxTextWidth;

    /**
     * The distance between the text and the background edge.
     */
    private float mTextPadding;

    /**
     * The distance between the focal edge and the text.
     */
    private float mFocalPadding;

    /**
     * The curveType to use for animations.
     */
    private int mAnimationCurveType;

    /**
     * The drawable to use to render the target.
     */
    @Nullable
    private Drawable mIconDrawable;

    /**
     * Should the back button press dismiss the prompt.
     */
    private boolean isBackButtonDismissEnabled = true;

    /**
     * Indicates how drawing area should be placed regarding to system status bar
     */
    private boolean isIgnoreStatusBar = false;

    /**
     * Listener for when the prompt state changes.
     */
    @Nullable
    private MaterialTapTargetPrompt.PromptStateChangeListener mPromptStateChangeListener;

    /**
     * Additional listener that can be set by other package classes for handling e.g. sequences of
     * prompts.
     */
    @Nullable
    private MaterialTapTargetPrompt.PromptStateChangeListener mSequencePromptStateChangeListener;
    private boolean isCaptureTouchEventOnFocal;
    private float isTextSeparation;
    private boolean isAutoDismiss = true;
    private boolean isAutoFinish = true;
    private boolean isCaptureTouchEventOutsidePrompt;
    private boolean isCaptureTouchEventonBackgroundPrompt = true;
    @Nullable
    private Font mPrimaryTextFont;
    @Nullable
    private Font mSecondaryTextFont;
    @Nullable
    private String mContentDescription;
    private int mPrimaryTextFontStyle;
    private int mSecondaryTextFontStyle;
    @Nullable
    private StateElement mIconDrawableTintList = null;
    @Nullable
    private BlendMode mIconDrawableTintMode = BlendMode.SRC_IN;
    private boolean isHasIconDrawableTint = true;
    private int mIconDrawableColourFilter = mBackgroundColour;
    @Nullable
    private Component mTargetRenderView;
    private boolean isIdleAnimationEnabled = true;
    private int mPrimaryTextGravity = LayoutAlignment.START;
    private int mSecondaryTextGravity = LayoutAlignment.START;
    @Nullable
    private Component mClipToView;

    /**
     * The shape to render for the prompt background.
     */
    private PromptBackground mPromptBackground = new CirclePromptBackground();

    /**
     * The shape to render for the prompt focal.
     */
    private PromptFocal mPromptFocal = new CirclePromptFocal();

    /**
     * The renderer for drawing the prompt text.
     */
    private PromptText mPromptText = new PromptText();

    /**
     * Constructor.
     *
     * @param resourceFinder The resource finder implementation to use to find resources.
     */
    public PromptOptions(final ResourceFinder resourceFinder) {
        mResourceFinder = resourceFinder;
        final float density = DensityUtils.getDeviceAttr(mResourceFinder.getContext()).getAttributes().densityPixels;
        mFocalRadius = density * Constants.NUM_44;
        mPrimaryTextSize = density * Constants.NUM_22;
        mSecondaryTextSize = density * Constants.NUM_18;
        mMaxTextWidth = density * Constants.NUM_400;
        mTextPadding = density * Constants.NUM_40;
        mFocalPadding = density * Constants.NUM_20;
        isTextSeparation = density * Constants.NUM_16;
    }

    /**
     * Loads the supplied theme into the prompt overwriting any previously set values if they are set in the theme.
     *
     * @param styleBean styleBean
     */
    public void load(StyleBean styleBean) {
        mPrimaryTextColour = styleBean.getMttpPrimaryTextColour();
        mSecondaryTextColour = styleBean.getMttpSecondaryTextColour();
        mPrimaryText = styleBean.getMttpPrimaryText();
        mSecondaryText = styleBean.getMttpSecondaryText();
        mBackgroundColour = styleBean.getMttpBackgroundColour();
        mFocalColour = styleBean.getMttpFocalColour();
        mFocalRadius = styleBean.getMttpFocalRadius();
        mPrimaryTextSize = styleBean.getMttpPrimaryTextSize();
        mSecondaryTextSize = styleBean.getMttpSecondaryTextSize();
        mMaxTextWidth = styleBean.getMttpMaxTextWidth();
        mTextPadding = styleBean.getMttpTextPadding();
        mFocalPadding = styleBean.getMttpFocalToTextPadding();
        isTextSeparation = styleBean.getMttpTextSeparation();
        isAutoDismiss = styleBean.isMttpAutoDismiss();
        isAutoFinish = styleBean.isMttpAutoFinish();
        isCaptureTouchEventOutsidePrompt = styleBean.isMttpCaptureTouchEventOutsidePrompt();
        isCaptureTouchEventOnFocal = styleBean.isMttpCaptureTouchEventOnFocal();
        isCaptureTouchEventonBackgroundPrompt = styleBean.isMttpCaptureTouchEventOnBackgroundPrompt();
        mPrimaryTextFontStyle = styleBean.getMttpPrimaryTextStyle();
        mSecondaryTextFontStyle = styleBean.getMttpSecondaryTextStyle();
        mPrimaryTextFont = PromptUtils.setFontFromAttrs(
                styleBean.getMttpPrimaryTextFontFamily(),
                styleBean.getMttpPrimaryTextFont(), mPrimaryTextFontStyle);
        mSecondaryTextFont = PromptUtils.setFontFromAttrs(
                styleBean.getMttpSecondaryTextFontFamily(),
                styleBean.getMttpSecondaryTextFont(), mSecondaryTextFontStyle);
        mContentDescription = styleBean.getMttpContentDescription();
        mIconDrawableTintList = styleBean.getMttpIconTint();
        mIconDrawableTintMode = PromptUtils.parseTintMode(
                styleBean.getMttpIconTintMode(), mIconDrawableTintMode);
        mIconDrawableColourFilter = styleBean.getMttpIconColourFilter();
        isHasIconDrawableTint = true;
        final int targetId = styleBean.getMttpTarget();

        if (targetId != 0) {
            mTargetView = mResourceFinder.findViewById(targetId);
            if (mTargetView != null) {
                isTargetSet = true;
            }
        }
    }

    /**
     * Get the resource finder being used.
     *
     * @return The resource finder being used.
     */
    public ResourceFinder getResourceFinder() {
        return mResourceFinder;
    }

    /**
     * Set the view for the prompt to focus on.
     *
     * @param target The view that the prompt will highlight.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setTarget(@Nullable final Component target) {
        mTargetView = target;
        mTargetPosition = null;
        isTargetSet = mTargetView != null;
        return (T) this;
    }

    /**
     * Set the view for the prompt to focus on using the given resource id.
     *
     * @param target The view that the prompt will highlight.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setTarget(final int target) {
        mTargetView = mResourceFinder.findViewById(target);
        mTargetPosition = null;
        isTargetSet = mTargetView != null;
        return (T) this;
    }

    /**
     * Set the centre point as a screen position
     *
     * @param left Centre point from screen left
     * @param top Centre point from screen top
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setTarget(final float left, final float top) {
        mTargetView = null;
        mTargetPosition = new Point(left, top);
        isTargetSet = true;
        return (T) this;
    }

    /**
     * Gets the view that the prompt is targeting.
     *
     * @return The target view or null if not set or targeting a position.
     */
    @Nullable
    public Component getTargetView() {
        return mTargetView;
    }

    /**
     * Get the position on the screen that is being targeted.
     *
     * @return The target position or null if targeting a view.
     */
    @Nullable
    public Point getTargetPosition() {
        return mTargetPosition;
    }

    /**
     * Change the view that is rendered as the target.
     * By default the view from {@link #setTarget(Component)} is rendered as the target.
     *
     * @param view The view to use to render the prompt target
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setTargetRenderView(@Nullable final Component view) {
        mTargetRenderView = view;
        return (T) this;
    }

    /**
     * Get the view that is rendered as the target.
     *
     * @return The view used to render the prompt target.
     */
    @Nullable
    public Component getTargetRenderView() {
        return mTargetRenderView;
    }

    /**
     * Has the target been set successfully?
     *
     * @return True if set successfully.
     */
    public boolean isTargetSet() {
        return isTargetSet;
    }

    /**
     * Set the primary text using the given resource id.
     *
     * @param resId The string resource id for the primary text
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setPrimaryText(final int resId) {
        mPrimaryText = mResourceFinder.getString(resId);
        return (T) this;
    }

    /**
     * Set the primary text to the given string
     *
     * @param text The primary text
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setPrimaryText(@Nullable final String text) {
        mPrimaryText = text;
        return (T) this;
    }

    /**
     * Set the primary text to the given CharSequence.
     * It is recommended that you don't go crazy with custom Spannables.
     *
     * @param text The primary text as CharSequence
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setPrimaryText(@Nullable final CharSequence text) {
        mPrimaryText = text;
        return (T) this;
    }

    /**
     * Get the text to draw for the primary text.
     *
     * @return The primary text.
     */
    @Nullable
    public CharSequence getPrimaryText() {
        return mPrimaryText;
    }

    /**
     * Set the primary text font size.
     *
     * @param size The primary text font size
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setPrimaryTextSize(final float size) {
        mPrimaryTextSize = size;
        return (T) this;
    }

    /**
     * Set the primary text font size using the given resource id.
     *
     * @param resId The resource id for the primary text size
     * @return This Builder object to allow for chaining of calls to set methods
     * @throws NotExistException NotExistException
     * @throws WrongTypeException WrongTypeException
     * @throws IOException IOException
     */
    public T setPrimaryTextSize(final int resId) throws NotExistException, WrongTypeException, IOException {
        mPrimaryTextSize = mResourceFinder.getResources().getElement(resId).getFloat();
        return (T) this;
    }

    /**
     * Get the primary text font size.
     *
     * @return The primary text font size.
     */
    public float getPrimaryTextSize() {
        return mPrimaryTextSize;
    }

    /**
     * Set the primary text colour.
     *
     * @param colour The primary text colour resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setPrimaryTextColour(final int colour) {
        mPrimaryTextColour = colour;
        return (T) this;
    }

    /**
     * Gets the primary text font colour.
     *
     * @return The primary text font colour.
     */
    public int getPrimaryTextColour() {
        return mPrimaryTextColour;
    }

    /**
     * Sets the font and style used to display the primary text.
     *
     * @param font The primary text font
     * @return The primary text font.
     */
    public T setPrimaryTextFont(@Nullable final Font font) {
        return setPrimaryTextFont(font, 0);
    }

    /**
     * Sets the font used to display the primary text.
     *
     * @param font The primary text font
     * @param style The font style
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setPrimaryTextFont(@Nullable final Font font, final int style) {
        mPrimaryTextFont = font;
        mPrimaryTextFontStyle = style;
        return (T) this;
    }

    /**
     * Get the font for the primary text.
     *
     * @return The primary text font.
     */
    @Nullable
    public Font getPrimaryTextFont() {
        return mPrimaryTextFont;
    }

    /**
     * Get the primary text font style.
     *
     * @return the primary text font style.
     */
    public int getPrimaryTextFontStyle() {
        return mPrimaryTextFontStyle;
    }

    /**
     * Set the secondary text using the given resource id.
     *
     * @param resId The secondary text resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setSecondaryText(final int resId) {
        mSecondaryText = mResourceFinder.getString(resId);
        return (T) this;
    }

    /**
     * Set the secondary text.
     *
     * @param text The secondary text
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setSecondaryText(@Nullable final String text) {
        mSecondaryText = text;
        return (T) this;
    }

    /**
     * Set the secondary text.
     * It is recommended that you don't go crazy with custom Spannables.
     *
     * @param text The secondary text as a CharSequence
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setSecondaryText(@Nullable final CharSequence text) {
        mSecondaryText = text;
        return (T) this;
    }

    /**
     * Get the secondary text.
     *
     * @return The secondary text.
     */
    @Nullable
    public CharSequence getSecondaryText() {
        return mSecondaryText;
    }

    /**
     * Set the secondary text font size using the give resource id.
     *
     * @param resId The secondary text string resource id
     * @return This Builder object to allow for chaining of calls to set methods
     * @throws NotExistException NotExistException
     * @throws WrongTypeException WrongTypeException
     * @throws IOException IOException
     */
    public T setSecondaryTextSize(final int resId) throws NotExistException, WrongTypeException, IOException {
        mSecondaryTextSize = mResourceFinder.getResources().getElement(resId).getFloat();
        return (T) this;
    }

    /**
     * Set the secondary text font size.
     *
     * @param size The secondary text font size
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setSecondaryTextSize(final float size) {
        mSecondaryTextSize = size;
        return (T) this;
    }

    /**
     * Get the secondary text size.
     *
     * @return The secondary text size.
     */
    public float getSecondaryTextSize() {
        return mSecondaryTextSize;
    }

    /**
     * Set the secondary text colour.
     *
     * @param colour The secondary text colour resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setSecondaryTextColour(final int colour) {
        mSecondaryTextColour = colour;
        return (T) this;
    }

    /**
     * Get the secondary text colour.
     *
     * @return The secondary text colour.
     */
    public int getSecondaryTextColour() {
        return mSecondaryTextColour;
    }

    /**
     * Sets the font used to display the secondary text.
     *
     * @param font The secondary text font
     * @return The secondary text font
     */
    public T setSecondaryTextFont(@Nullable final Font font) {
        return setSecondaryTextFont(font, 0);
    }

    /**
     * Sets the font and style used to display the secondary text.
     *
     * @param font The secondary text font
     * @param style The font style
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setSecondaryTextFont(@Nullable final Font font, final int style) {
        mSecondaryTextFont = font;
        mSecondaryTextFontStyle = style;
        return (T) this;
    }

    /**
     * Get the secondary text font.
     *
     * @return The secondary text font.
     */
    @Nullable
    public Font getSecondaryTextFont() {
        return mSecondaryTextFont;
    }

    /**
     * Get the secondary text font style.
     *
     * @return The secondary text font style.
     */
    public int getSecondaryTextFontStyle() {
        return mSecondaryTextFontStyle;
    }

    /**
     * Set the accessibility content description text using the given resource id.
     *
     * @param resId The string resource id for the accessibility content description text
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setContentDescription(final int resId) {
        mContentDescription = mResourceFinder.getString(resId);
        return (T) this;
    }

    /**
     * Set the accessibility content description text to the given string
     *
     * @param text The accessibility content description text
     * @return This Builder object to allow for chaining of calls to set methods
     */

    public T setContentDescription(@Nullable final String text) {
        mContentDescription = text;
        return (T) this;
    }

    /**
     * Get the text for the accessibility content description.
     * Defaults to a concatenation of primary and secondary texts.
     *
     * @return The accessibility content description text.
     */
    @Nullable
    public String getContentDescription() {
        if (mContentDescription != null) {
            return mContentDescription;
        } else {
            return String.format("%s. %s", mPrimaryText, mSecondaryText);
        }
    }

    /**
     * Set the text left and right padding using the given resource id.
     *
     * @param resId The text padding dimension resource id
     * @return This Builder object to allow for chaining of calls to set methods
     * @throws NotExistException NotExistException
     * @throws WrongTypeException WrongTypeException
     * @throws IOException IOException
     */
    public T setTextPadding(final int resId) throws NotExistException, WrongTypeException, IOException {
        mTextPadding = mResourceFinder.getResources().getElement(resId).getFloat();
        return (T) this;
    }

    /**
     * Set the text left and right padding.
     *
     * @param padding The padding on the text left and right
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setTextPadding(final float padding) {
        mTextPadding = padding;
        return (T) this;
    }

    /**
     * Get the text left and right padding.
     *
     * @return The text left and right padding.
     */
    public float getTextPadding() {
        return mTextPadding;
    }

    /**
     * Set the distance between the primary and secondary text using the given resource id.
     *
     * @param resId The dimension resource id for the text separation
     * @return This Builder object to allow for chaining of calls to set methods
     * @throws NotExistException NotExistException
     * @throws WrongTypeException WrongTypeException
     * @throws IOException IOException
     */
    public T setTextSeparation(final int resId) throws NotExistException, WrongTypeException, IOException {
        isTextSeparation = mResourceFinder.getResources().getElement(resId).getFloat();
        return (T) this;
    }

    /**
     * Set the distance between the primary and secondary text.
     *
     * @param separation The distance separation between the primary and secondary text
     * @return This Builder object to allow for chaining of calls to set methods
     */

    public T setTextSeparation(final float separation) {
        isTextSeparation = separation;
        return (T) this;
    }

    /**
     * Get the distance between the primary and secondary text.
     *
     * @return the distance between the primary and secondary text.
     */
    public float getTextSeparation() {
        return isTextSeparation;
    }

    /**
     * Set the padding between the text and the focal point using the given resource id.
     *
     * @param resId The dimension resource id for the focal to text distance
     * @return This Builder object to allow for chaining of calls to set methods
     * @throws NotExistException NotExistException
     * @throws WrongTypeException WrongTypeException
     * @throws IOException IOException
     */
    public T setFocalPadding(final int resId) throws NotExistException, WrongTypeException, IOException {
        mFocalPadding = mResourceFinder.getResources().getElement(resId).getFloat();
        return (T) this;
    }

    /**
     * Set the padding between the text and the focal point.
     *
     * @param padding The distance between the text and focal
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setFocalPadding(final float padding) {
        mFocalPadding = padding;
        return (T) this;
    }

    /**
     * Get the padding between the text and the focal.
     *
     * @return The padding between the text and the focal.
     */
    public float getFocalPadding() {
        return mFocalPadding;
    }

    /**
     * Set the curveType to use in animations.
     *
     * @param curveType The animation curveType to use
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setAnimationCurveType(@Nullable final int curveType) {
        mAnimationCurveType = curveType;
        return (T) this;
    }

    /**
     * Get the animation curveType that is used.
     *
     * @return The animation curveType that is used.
     */
    @Nullable
    public int getAnimationCurveType() {
        return mAnimationCurveType;
    }

    /**
     * Enable/disable focal animation.
     * true by default
     *
     * @param isEnabled Idle animation enabled
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setIdleAnimationEnabled(final boolean isEnabled) {
        isIdleAnimationEnabled = isEnabled;
        return (T) this;
    }

    /**
     * Is the focal animation enabled.
     *
     * @return True if the idle animation is enabled.
     */
    public boolean getIdleAnimationEnabled() {
        return isIdleAnimationEnabled;
    }

    /**
     * Set the icon to draw in the focal point using the given resource id.
     *
     * @param element The drawable resource id for the icon
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setIcon(final Element element) {
        mIconDrawable = mResourceFinder.getDrawable(element);
        return (T) this;
    }

    /**
     * Set the icon to draw in the focal point using the given resource id.
     *
     * @param pixelMap The drawable resource id for the icon
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setIcon(final PixelMap pixelMap) {
        mIconDrawable = mResourceFinder.getDrawable(pixelMap);
        return (T) this;
    }

    /**
     * Set the icon to draw in the focal point.
     *
     * @param drawable The drawable for the icon
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setIconDrawable(@Nullable final Drawable drawable) {
        mIconDrawable = drawable;
        return (T) this;
    }

    /**
     * Get the icon drawn as the target.
     *
     * @return The icon drawn as the target.
     */
    @Nullable
    public Drawable getIconDrawable() {
        return mIconDrawable;
    }

    /**
     * Applies a tint to the icon drawable
     *
     * @param tint the tint to apply to the icon drawable, {@code null} will remove the tint.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setIconDrawableTintList(@Nullable StateElement tint) {
        mIconDrawableTintList = tint;
        isHasIconDrawableTint = tint != null;
        return (T) this;
    }

    /**
     * Sets the PorterDuff mode to use to apply the tint.
     *
     * @param tintMode the tint mode to use on the icon drawable, {@code null} will remove the
     * tint.
     *
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setIconDrawableTintMode(@Nullable BlendMode tintMode) {
        mIconDrawableTintMode = tintMode;
        if (tintMode == null) {
            mIconDrawableTintList = null;
            isHasIconDrawableTint = false;
        }
        return (T) this;
    }

    /**
     * Sets the colour to use to tint the icon drawable.
     *
     * @param colour The colour to use to tint the icon drawable, call {@link
     * #setIconDrawableTintList(StateElement)} or {@link
     * #setIconDrawableTintMode(BlendMode)} with {@code null} to remove the
     * tint.
     *
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setIconDrawableColourFilter(final int colour) {
        mIconDrawableColourFilter = colour;
        mIconDrawableTintList = null;
        isHasIconDrawableTint = true;
        return (T) this;
    }

    /**
     * Set the listener to listen for when the prompt state changes.
     *
     * @param listener The listener to use
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setPromptStateChangeListener(
            @Nullable final MaterialTapTargetPrompt.PromptStateChangeListener listener) {
        mPromptStateChangeListener = listener;
        return (T) this;
    }

    /**
     * Set the internal listener to listen for when the prompt state changes.
     * This does not return a builder is it's not intended to be user during the
     * creation of Prompts
     *
     * @param listener The listener to use
     */
    public void setSequenceListener(
            @Nullable final MaterialTapTargetPrompt.PromptStateChangeListener listener) {
        mSequencePromptStateChangeListener = listener;
    }

    /**
     * Handles emitting the prompt state changed events.
     *
     * @param state The state that the prompt is now in.
     * @param prompt prompt
     */
    public void onPromptStateChanged(final MaterialTapTargetPrompt prompt, final int state) {
        if (mPromptStateChangeListener != null) {
            mPromptStateChangeListener.onPromptStateChanged(prompt, state);
        }
    }

    /**
     * Handles emitting the additional prompt state changed events.
     *
     * @param state The state that the prompt is now in.
     * @param prompt prompt
     */
    public void onExtraPromptStateChanged(final MaterialTapTargetPrompt prompt, final int state) {
        if (mSequencePromptStateChangeListener != null) {
            mSequencePromptStateChangeListener.onPromptStateChanged(prompt, state);
        }
    }

    /**
     * Set if the prompt should stop touch events on the focal point from passing to underlying
     * views. Default is false.
     *
     * @param isCaptureTouchEvent True to capture touch events in the prompt
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setCaptureTouchEventOnFocal(final boolean isCaptureTouchEvent) {
        isCaptureTouchEventOnFocal = isCaptureTouchEvent;
        return (T) this;
    }

    /**
     * Get if the prompt should stop touch events on the focal point from passing to underlying
     * views.
     *
     * @return True to capture touch events in the prompt
     */
    public boolean getCaptureTouchEventOnFocal() {
        return isCaptureTouchEventOnFocal;
    }

    /**
     * Set the max width that the primary and secondary text can be using the given resource
     * id.
     *
     * @param resId The dimension resource id for the max width that the text can reach
     * @return This Builder object to allow for chaining of calls to set methods
     * @throws NotExistException NotExistException
     * @throws WrongTypeException WrongTypeException
     * @throws IOException IOException
     */
    public T setMaxTextWidth(final int resId) throws NotExistException, WrongTypeException, IOException {
        mMaxTextWidth = mResourceFinder.getResources().getElement(resId).getFloat();
        return (T) this;
    }

    /**
     * Set the max width that the primary and secondary text can be.
     *
     * @param width The max width that the text can reach
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setMaxTextWidth(final float width) {
        mMaxTextWidth = width;
        return (T) this;
    }

    /**
     * Get the maximum width that the primary and secondary text can be.
     *
     * @return The maximum text width.
     */
    public float getMaxTextWidth() {
        return mMaxTextWidth;
    }

    /**
     * Set the background colour.
     * The Material Design Guidelines specify that this should be 244 or hex F4.
     *
     * @param colour The background colour colour resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setBackgroundColour(final int colour) {
        mBackgroundColour = colour;
        return (T) this;
    }

    /**
     * Get the background colour.
     *
     * @return The background colour.
     */
    public int getBackgroundColour() {
        return mBackgroundColour;
    }

    /**
     * Set the focal point colour.
     *
     * @param colour The focal colour colour resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setFocalColour(final int colour) {
        mFocalColour = colour;
        return (T) this;
    }

    /**
     * Get the focal point colour.
     *
     * @return The focal point colour.
     */
    public int getFocalColour() {
        return mFocalColour;
    }

    /**
     * Set the focal point radius using the given resource id.
     *
     * @param resId The focal radius dimension resource id
     * @return This Builder object to allow for chaining of calls to set methods
     * @throws NotExistException NotExistException
     * @throws WrongTypeException WrongTypeException
     * @throws IOException IOException
     */
    public T setFocalRadius(final int resId) throws NotExistException, WrongTypeException, IOException {
        mFocalRadius = mResourceFinder.getResources().getElement(resId).getFloat();
        return (T) this;
    }

    /**
     * Set the focal point radius.
     *
     * @param radius The focal radius
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setFocalRadius(final float radius) {
        mFocalRadius = radius;
        return (T) this;
    }

    /**
     * Get the focal point radius for the circle prompt focal.
     *
     * @return The radius used for the circle prompt focal.
     */
    public float getFocalRadius() {
        return mFocalRadius;
    }

    /**
     * Set whether the prompt should dismiss itself when a touch event occurs outside the focal.
     * Default is true.
     *
     * Listen for the {@link MaterialTapTargetPrompt#STATE_NON_FOCAL_PRESSED} event in the
     * {@link #setPromptStateChangeListener(MaterialTapTargetPrompt.PromptStateChangeListener)} to handle the prompt
     * being pressed outside the focal area.
     *
     * @param isAuto True - prompt will dismiss when touched outside the focal, false - no
     * action taken.
     *
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setAutoDismiss(final boolean isAuto) {
        isAutoDismiss = isAuto;
        return (T) this;
    }

    /**
     * Get whether the prompt should dismiss itself when a touch event occurs outside the focal.
     *
     * @return True - prompt will dismiss when touched outside the focal, false - no action taken.
     */
    public boolean getAutoDismiss() {
        return isAutoDismiss;
    }

    /**
     * Set whether the prompt should finish itself when a touch event occurs inside the focal.
     * Default is true.
     *
     * Listen for the {@link MaterialTapTargetPrompt#STATE_FOCAL_PRESSED} event in the
     * {@link #setPromptStateChangeListener(MaterialTapTargetPrompt.PromptStateChangeListener)} to handle the prompt
     * target being pressed.
     *
     * @param isAuto True - prompt will finish when touched inside the focal, false - no
     * action taken.
     *
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setAutoFinish(final boolean isAuto) {
        isAutoFinish = isAuto;
        return (T) this;
    }

    /**
     * Get if the prompt should finish itself when a touch event occurs inside the focal.
     *
     * @return True if the prompt should finish itself when a touch event occurs inside the focal.
     */
    public boolean getAutoFinish() {
        return isAutoFinish;
    }

    /**
     * Set if the prompt should stop touch events outside the prompt from passing to underlying
     * views. Default is false.
     *
     * @param isPrompt True to capture touch events out side the prompt
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setCaptureTouchEventOutsidePrompt(
            final boolean isPrompt) {
        isCaptureTouchEventOutsidePrompt = isPrompt;
        return (T) this;
    }

    /**
     * Get if the prompt should stop touch events outside the prompt from passing to underlying views.
     *
     * @return True if touch events will not be passed to views below the prompt.
     */
    public boolean getCaptureTouchEventOutsidePrompt() {
        return isCaptureTouchEventOutsidePrompt;
    }

    /**
     * Set if the prompt should stop touch events on the focal point from passing to underlying
     * views. Default is false.
     *
     * @param isCaptureBackgroundTouchEvent True to capture touch events in the prompt
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setCaptureTouchEventOnBackgroundPrompt(final boolean isCaptureBackgroundTouchEvent) {
        isCaptureTouchEventonBackgroundPrompt = isCaptureBackgroundTouchEvent;
        return (T) this;
    }

    /**
     * Get if the prompt should stop touch events outside the prompt from passing to underlying views.
     *
     * @return True if touch events will not be passed to views below the prompt.
     */
    public boolean getCaptureTouchEventOnBackgroundPrompt() {
        return isCaptureTouchEventonBackgroundPrompt;
    }

    /**
     * Set the primary and secondary text horizontal layout gravity.
     *
     * @param gravity The horizontal gravity
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setTextGravity(final int gravity) {
        mPrimaryTextGravity = gravity;
        mSecondaryTextGravity = gravity;
        return (T) this;
    }

    /**
     * Set the primary text horizontal layout gravity.
     *
     * @param gravity The horizontal gravity
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setPrimaryTextGravity(final int gravity) {
        mPrimaryTextGravity = gravity;
        return (T) this;
    }

    /**
     * Gets the gravity for the primary text.
     *
     * @return The primary texts gravity.
     */
    public int getPrimaryTextGravity() {
        return mPrimaryTextGravity;
    }

    /**
     * Set the secondary text horizontal layout gravity.
     *
     * @param gravity The horizontal gravity
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setSecondaryTextGravity(final int gravity) {
        mSecondaryTextGravity = gravity;
        return (T) this;
    }

    /**
     * Gets the gravity for the secondary text.
     *
     * @return The secondary texts gravity.
     */
    public int getSecondaryTextGravity() {
        return mSecondaryTextGravity;
    }

    /**
     * Set the view to clip the prompt to.
     * The prompt won't draw outside the bounds of this view.
     * <p>
     * Null can be used to stop the prompt being clipped to a view.
     *
     * @param view The view to clip to
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setClipToView(@Nullable final Component view) {
        mClipToView = view;
        return (T) this;
    }

    /**
     * Get the view that the prompt canvas is clipped to.
     * The prompt won't draw outside the bounds of this view.
     *
     * @return The view that the prompt canvas is clipped to.
     */
    @Nullable
    public Component getClipToView() {
        return mClipToView;
    }

    /**
     * Back button can be used to dismiss the prompt.
     * Default: true
     *
     * @param isEnabled True for back button dismiss enabled
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setBackButtonDismissEnabled(final boolean isEnabled) {
        isBackButtonDismissEnabled = isEnabled;
        return (T) this;
    }

    /**
     * Will the pressing the system back button dismiss the prompt.
     *
     * @return True if pressing the system back button will dismiss the prompt, false otherwise.
     */
    public boolean getBackButtonDismissEnabled() {
        return isBackButtonDismissEnabled;
    }

    /**
     * Indicates whether to ignore system status bar. Drawing area will be increased to the top of
     * screen regardless of status bar if this flag is true (status bar should be transparent to see
     * any effect from this)
     * Default: false
     *
     * @param isEnabled true for drawing behind system status bar
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setIgnoreStatusBar(final boolean isEnabled) {
        isIgnoreStatusBar = isEnabled;
        return (T) this;
    }

    /**
     * Get ignore status bar flag
     *
     * @return true if status bar should be ignored, otherwise false
     */
    public boolean getIgnoreStatusBar() {
        return isIgnoreStatusBar;
    }

    /**
     * Sets the renderer for the prompt background.
     *
     * @param promptBackground The background shape to use.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setPromptBackground(final PromptBackground promptBackground) {
        mPromptBackground = promptBackground;
        return (T) this;
    }

    /**
     * Get the prompt focal renderer.
     *
     * @return The prompt focal instance.
     */
    public PromptBackground getPromptBackground() {
        return mPromptBackground;
    }

    /**
     * Sets the renderer for the prompt focal.
     *
     * @param promptFocal The focal shape to use.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setPromptFocal(final PromptFocal promptFocal) {
        mPromptFocal = promptFocal;
        return (T) this;
    }

    /**
     * Get the prompt focal renderer.
     *
     * @return The prompt focal instance.
     */
    public PromptFocal getPromptFocal() {
        return mPromptFocal;
    }

    /**
     * Set the {@link PromptText} implementation to use to render the prompt text.
     *
     * @param promptText The prompt text implementation.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public T setPromptText(final PromptText promptText) {
        mPromptText = promptText;
        return (T) this;
    }

    /**
     * Get the {@link PromptText} implementation used to render the prompt text.
     *
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public PromptText getPromptText() {
        return mPromptText;
    }

    /**
     * Creates an {@link MaterialTapTargetPrompt} with the arguments supplied to this
     * builder.
     * <p>
     * Calling this method does not display the prompt. If no additional
     * processing is needed, {@link #show()} may be called instead to both
     * create and display the prompt.
     * </p>
     * <p>
     * Will return null if a valid target has not been set or the primary text is null.
     * To check that a valid target has been set call {@link #isTargetSet()}.
     * </p>
     *
     * @return The created builder or null if no target
     */
    @Nullable
    public MaterialTapTargetPrompt create() {
        if (!isTargetSet || (mPrimaryText == null && mSecondaryText == null)) {
            return null;
        }
        final MaterialTapTargetPrompt prompt = MaterialTapTargetPrompt.createDefault(this);
        if (mAnimationCurveType == Constants.NUM_F1) {
            mAnimationCurveType = Animator.CurveType.ACCELERATE_DECELERATE;
        }
        if (mIconDrawable != null) {
            if (isHasIconDrawableTint) {
                if (mIconDrawableTintList != null) {
                    mIconDrawable.setImageElement(mIconDrawableTintList);
                } else {
                    mIconDrawable.setImageTint(mIconDrawableColourFilter, mIconDrawableTintMode);
                    mIconDrawable.setAlpha(Color.alpha(mIconDrawableColourFilter));
                }
            }
        }
        mPromptBackground.setColour(getBackgroundColour());
        mPromptFocal.setColour(getFocalColour());
        mPromptFocal.setRippleAlpha(Constants.NUM_150);
        mPromptFocal.setDrawRipple(getIdleAnimationEnabled());
        if (mPromptFocal instanceof CirclePromptFocal) {
            ((CirclePromptFocal) mPromptFocal).setRadius(getFocalRadius());
        }
        return prompt;
    }

    /**
     * Creates a {@link MaterialTapTargetPrompt} with the arguments supplied to this
     * builder and immediately displays the prompt.
     * <p>
     * Calling this method is functionally identical to:
     * </p>
     * <pre>
     * MaterialTapTargetPrompt prompt = builder.create();
     * prompt.show();
     * </pre>
     * <p>
     * Will return null if a valid target has not been set or the primary text and secondary
     * text are null.
     * To check that a valid target has been set call {@link #isTargetSet()}.
     * </p>
     *
     * @return The created builder or null if no target
     */
    @Nullable
    public MaterialTapTargetPrompt show() {
        final MaterialTapTargetPrompt prompt = create();
        if (prompt != null) {
            prompt.show();
        }
        return prompt;
    }

    /**
     * Creates a {@link MaterialTapTargetPrompt} with the arguments supplied to this
     * builder and immediately displays the prompt for the number of milliseconds supplied.
     * <p>
     * Calling this method is functionally identical to:
     * </p>
     * <pre>
     * MaterialTapTargetPrompt prompt = builder.create();
     * prompt.showFor(milliseconds);
     * </pre>
     * <p>
     * Will return null if a valid target has not been set or the primary text and secondary
     * text are null.
     * To check that a valid target has been set call {@link #isTargetSet()}.
     * </p>
     *
     * @param milliseconds The number of milliseconds to show the prompt for.
     * @return The created builder or null if no target
     */
    @Nullable
    public MaterialTapTargetPrompt showFor(final long milliseconds) {
        final MaterialTapTargetPrompt prompt = create();
        if (prompt != null) {
            prompt.showFor(milliseconds);
        }
        return prompt;
    }
}
